﻿using Swimj.Core.Utilities;
using System.Collections;
using System.ComponentModel;
using System.Linq.Expressions;
using System.Reflection;

namespace Swimj.Core.Extensions
{
    public static class ClassMappingExtensions
    {
        private static ClassMapper Mapper(ClassMappingSettings settings)
        {
            if (ClassMapper.defaultClassMapperInstance != null)
            {
                var result = ClassMapper.defaultClassMapperInstance;
                if (settings != null) // Only set settings if we get some, otherwise instance settings should be used
                    result.SetSettings(settings);
                return result;
            }

            return new ClassMapper().SetSettings(settings ?? ClassMappingSettings.Default);
        }

        public static object MapTo<TInput>(this TInput input, Type tResult, ClassMappingSettings? settings = null)
        {
            return Mapper(settings).Map(input, tResult);
        }

        public static Task<object> MapToAsync<TInput>(this TInput input, Type tResult, ClassMappingSettings? settings = null)
        {
            return Mapper(settings).MapAsync(input, tResult);
        }

        public static TResult MapTo<TInput, TResult>(this TInput input, ClassMappingSettings settings,
            params Action<TResult, TInput>[] differentMappingAssignments)
        {
            return Mapper(settings).Map(input, differentMappingAssignments);
        }

        public static TResult MapTo<TInput, TResult>(this TInput input,
            params Action<TResult, TInput>[] differentMappingAssignments)
        {
            return input.MapTo(null, differentMappingAssignments);
        }

        public static TResult MapTo<TResult>(this object input, params TypeConverter[] specificConverters)
        {
            return MapTo<object, TResult>(input,
                ClassMappingSettings.Default.Set(s => s.AddConverters(specificConverters)));
        }

        public static TResult MapTo<TResult>(this object input, ClassMappingSettings settings)
        {
            return MapTo<object, TResult>(input, settings);
        }


        public static Task<TResult> MapToAsync<TInput, TResult>(this TInput input, ClassMappingSettings settings,
            params Action<TResult, TInput>[] differentMappingAssignments)
        {
            return Mapper(settings).MapAsync(input, differentMappingAssignments);
        }

        public static IEnumerable<T> MapElementsTo<T>(this IEnumerable enumerable, ClassMappingSettings settings = null)
        {
            return enumerable.MapTo<IEnumerable<T>>(settings ?? ClassMappingSettings.Default);
        }


        public static Task<TResult> MapToAsync<TInput, TResult>(this TInput input,
            params Action<TResult, TInput>[] differentMappingAssignments)
        {
            return input.MapToAsync(null, differentMappingAssignments);
        }

        public static Task<TResult> MapToAsync<TResult>(this object input, params TypeConverter[] specificConverters)
        {
            return MapToAsync<object, TResult>(input,
                ClassMappingSettings.Default.Set(s => s.AddConverters(specificConverters)));
        }

        public static Task<TResult> MapToAsync<TResult>(this object input, ClassMappingSettings settings)
        {
            return MapToAsync<object, TResult>(input, settings);
        }

        public static ClassMappingSettings Set<T>(this ClassMappingSettings settings,
            Expression<Func<ClassMappingSettings, T>> memberExpression, T value)
        {
            string memberName = memberExpression.GetMemberName();
            var prop = Check.NotNull(() => settings.GetType().GetProperty(memberName));
            prop.SetValue(settings, value, null);
            return settings;
        }

        public static ClassMappingSettings Set(this ClassMappingSettings settings, params Action<ClassMappingSettings>[] o)
        {
            o.Apply(action => action(settings));
            return settings;
        }

        public static ClassMappingSettings Settings(
            this Tuple<ClassMappingSettings, MemberInfo, MemberInfo> tpl)
        {
            return tpl.Item1;
        }

        public static ClassMappingSettings Settings(
            this Tuple<ClassMappingSettings, MemberInfo> tpl)
        {
            return tpl.Item1;
        }

        public static Tuple<ClassMappingSettings, MemberInfo> Assign<TInput>(this ClassMappingSettings settings,
            Expression<Func<TInput, object>> inProp)
        {
            return Tuple.Create(settings, inProp.GetMemberInfo());
        }

        public static Tuple<ClassMappingSettings, MemberInfo, MemberInfo> To<TResult>(
            this Tuple<ClassMappingSettings, MemberInfo> tpl,
            Expression<Func<TResult, object>> outProp)
        {
            var memberInfo = outProp.GetMemberInfo();
            return Tuple.Create(tpl.Item1.AddAssignment(tpl.Item2, memberInfo), tpl.Item2, memberInfo);
        }

        public static Tuple<ClassMappingSettings, MemberInfo, MemberInfo> And<TResult>(
            this Tuple<ClassMappingSettings, MemberInfo, MemberInfo> tpl,
            Expression<Func<TResult, object>> outProp)
        {
            var memberInfo = outProp.GetMemberInfo();
            return Tuple.Create(tpl.Item1.AddAssignment(tpl.Item2, memberInfo), tpl.Item2, memberInfo);
        }
    }
}